using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;

namespace Meta {
	[Serializable]
	public class EvalStack : IInspectable, ISerializable {
#if THREADSAFE
		public static EvalStack CurrentThreadEvalStack {
			get {
				EvalStack probe = currentThreadEvalStack;  
				if (probe == null)
					return currentThreadEvalStack = new EvalStack();
				else
					return probe;
			}
		}

		[ThreadStatic()]
		static EvalStack currentThreadEvalStack;
#else
		public static EvalStack CurrentThreadEvalStack {
			get {
				return currentThreadEvalStack;
			}
		}

		static EvalStack currentThreadEvalStack = new EvalStack();
#endif

		public EvalStack() { }

		static public bool showAllExpressions = true;

		public const short stackSize = 1000;
		public const short argStackSize = 4000;

		//
		// The EvalStack holds information on all the activation frames of the running
		// Meta program, one per procedure call.  It's only purpose is to provide information
		// for the debugger should an error occur, since there isn't a native CLR interface for it.
		// In principle, we could get it by calling out to COM and having COM call back into the
		// CLR, but (a) that's a huge pain, and (b) then the debugger has to access objects
		// within its own address space as if they are remote COM objects.  Bleh.
		//
		// The EvalStack holds for each running frame:
		//   The procedure running; if it's a method call, then it also records the target of the method
		//   Its arglist
		//   The environment it's closed under
		//   The expression within the procedure currently executing
		//   The expression within the procedure being breakpointed, if any
		//
		// The elegant way to do this would be to have a Frame object with 6 fields in it.
		// However, that means we make 1000 6-word objects whenever we start a Meta
		// thread and then GC them when the thread exits.  It seems to run faster if we have 6
		// parallel arrays instead, so that's what we do.

		// Index of the next frame to be allocated.
		// Current frame is stackPointer-1;
		private short stackPointer = 0;
		// Procedure for each stack frame
		//    Procedure object if it's a normal call
		//    String if it's a CLR method invocation (in which case the method target is on the target stack)
		//    Exception if it's an exception frame
		private object[] procedureStack = new object[stackSize];
		private object[] targetStack = new object[stackSize];
		// Arguments for each stack frame
		//    Object[] if it's a normal argument list
		//    Short if it's a pointer into the argStack (for inlined arguments to reduce consing)
		private object[] arglistStack = new object[stackSize];
		// Environment of the procedure running in this frame.
		private Environment[] environmentStack = new Environment[stackSize];
		// Expression this frame is currently running.
		private Expression[] expressionStack = new Expression[stackSize];
		// Expression currently being breakpointed in this frame, if any.
		// used for implementing step-over functionality.
		private Expression[] breakpoint = new Expression[stackSize];

		// Inline stack for arguments to primitive procedures.  Saves having to allocate
		// and GC an object[] to hold the arguments.  Not used for calls to interpreted
		// procedures and other ApplyInterfaceProcedures, since they'd just have to
		// cons the arglist object anyway.
		public object[] argStack = new object [argStackSize];
		// Current pointer for the inline argStack.
		private short argStackSP = 0;

		void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
			info.AddValue("procedureStack", RemoteDataPlaceholder.PlaceholderArray(procedureStack, stackPointer));
			info.AddValue("targetStack", RemoteDataPlaceholder.PlaceholderArray(targetStack, stackPointer));
			object[] aStack = new object[stackPointer];
			for (int i=0; i<stackPointer; i++) {
				if (arglistStack[i] is short || arglistStack[i]==null)
					aStack[i] = arglistStack[i];
				else
					aStack[i] = RemoteDataPlaceholder.PlaceholderArray((object[])(arglistStack[i]));
			}
			info.AddValue("arglistStack", aStack);
			info.AddValue("argStack", RemoteDataPlaceholder.PlaceholderArray(argStack, argStackSP));
			info.AddValue("environmentStack", environmentStack);
			info.AddValue("expressionStack", expressionStack);
			info.AddValue("stackPointer", stackPointer);
			info.AddValue("argStackSP", argStackSP);
		}

		protected EvalStack(SerializationInfo info, StreamingContext context) {
			procedureStack = (object[])info.GetValue("procedureStack", typeof(object[]));
			// Try reading target stack, but try to continue if it's an old dump that doesn't include a targetStack.
			try {
				targetStack = (object[])info.GetValue("targetStack", typeof(object[]));
			} catch {
			}
			arglistStack = (object[])info.GetValue("arglistStack", typeof(object[]));
			argStack = (object[])info.GetValue("argStack", typeof(object[]));
			environmentStack = (Environment[])info.GetValue("environmentStack", typeof(Environment[]));
			expressionStack = (Expression[])info.GetValue("expressionStack", typeof(Expression[]));
			stackPointer = info.GetInt16("stackPointer");
			argStackSP = info.GetInt16("argStackSP");
		}

		[Serializable]
		public struct Mark {
			short sp;
			short asp;
			public Mark(EvalStack s) {
				sp = s.stackPointer;
				asp = s.argStackSP;
			}

			public void ResetToMark() {
				EvalStack s = CurrentThreadEvalStack;

				s.stackPointer = sp;
				s.argStackSP = asp;
			}
		}

		public Exception Exception {
			get {
				if (procedureStack[stackPointer-1] is Exception)
					return (Exception)procedureStack[stackPointer-1];
				else
					return null;
			}
		}

		public IList MapFrames(Procedure p) {
			int framecount = (Exception==null)?stackPointer: stackPointer-1;
			EvalStack running = CurrentThreadEvalStack;
			object[] result = new object[framecount];

			for (int frame=0; frame<framecount; frame++)
				result[frame] = Procedure.TracedCall(p, running, GetCall(frame));

			return result;
		}

		public void ForEachFrame(Procedure p) {
			int framecount = (Exception==null)?stackPointer: stackPointer-1;
			EvalStack running = CurrentThreadEvalStack;

			for (int frame=0; frame<framecount; frame++)
				Procedure.TracedCall(p, running, GetCall(frame));
		}

		public IList FilterFrames(Procedure p) {
			int framecount = (Exception==null)?stackPointer: stackPointer-1;
			EvalStack running = CurrentThreadEvalStack;
			ArrayList result = new ArrayList();

			for (int frame=0; frame<framecount; frame++) {
				IList call = GetCall(frame);
				if ((bool)(Procedure.TracedCall(p, running, call)))
					result.Add(call);
			}

			return result;
		}


		IList FrameArglist(int frame) {
			object args=arglistStack[frame];
			if (args is IList)
				return (IList)args;
			else if (args is short) {
				// Args is really an index into argStack.
				short framePointer = (short)args;
				int nargs = (int)argStack[framePointer];

				object[] argList = new object[nargs];
				for (int j=0; j<nargs; j++)
					argList[j] = argStack[framePointer+j+1];
				return argList;
			} else if (args==null)
				return new object[0];
			else
				throw new Exception("Invalid object on arglistStack");
		}

		public IList CurrentCall {
			get {
				if (Exception!=null)
					return GetCall(stackPointer-2);
				else
					return GetCall(stackPointer-1);
			}
		}

		public IList PreviousCall {
			get {
				if (Exception!=null)
					return (stackPointer>2)?(GetCall(stackPointer-3)):null;
				else
					return (stackPointer>1)?(GetCall(stackPointer-2)):null;
			}
		}

		public IList GetCall(int frame) {
			return new LinkedList(procedureStack[frame], FrameArglist(frame));
		}

		public Expression CurrentExpression {
			get {
				if (Exception!=null)
					return expressionStack[stackPointer-2];
				else
					return expressionStack[stackPointer-1];
			}
		}

		public Expression PreviousExpression {
			get {
				if (Exception!=null)
					return (stackPointer>2)?expressionStack[stackPointer-3]:null;
				else
					return (stackPointer>1)?expressionStack[stackPointer-2]:null;
			}
		}

		public void InvalidateRenderCache() {
			renderCache = null;
		}


		public short AllocFrame(short size) {
			short frame = argStackSP;
			argStackSP += size;
			return frame;
		}

		public void PopFrame(short location) {
			argStackSP = location;
		}

		private IList renderCache = null;

		public void Clear() {
			stackPointer=0;
			argStackSP = 0;
		}

		public bool IsEmpty {
			get {
				return stackPointer==0;
			}
		}

		public int TraceDepth {
			get {
				int depth = 0;
				for (int i=0; i<stackPointer; i++) {
					if (procedureStack[i]==null)
						continue;
					Procedure p = procedureStack[i] as Procedure;
					if (p!=null && p.Traced)
						depth++;
				}
				return depth;
			}
		}

		public void ClearForGC() {
			Clear();
			System.Array.Clear(procedureStack,0, procedureStack.Length);
			System.Array.Clear(arglistStack,0, arglistStack.Length);
			System.Array.Clear(environmentStack,0, arglistStack.Length);
			System.Array.Clear(argStack,0, argStack.Length);
		}


		public void ForcePush(object proc, object args) {
			procedureStack[stackPointer] = proc;
			arglistStack[stackPointer] = args;
			expressionStack[stackPointer] = null;
			breakpoint[stackPointer] = null;
			stackPointer++;
		}

		public void Push(object proc, object args) {
			if (stackPointer>=stackSize-1)
				throw new System.StackOverflowException("Your program recursed too deeply or went into an infinite recursion.");
			procedureStack[stackPointer] = proc;
			arglistStack[stackPointer] = args;
			expressionStack[stackPointer] = null;
			breakpoint[stackPointer] = null;
			stackPointer++;
		}

		public void PushMethodCall(object target, string method, object args) {
			if (stackPointer>=stackSize-1)
				throw new System.StackOverflowException("Your program recursed too deeply or went into an infinite recursion.");
			procedureStack[stackPointer] = method;
			targetStack[stackPointer] = target;
			arglistStack[stackPointer] = args;
			expressionStack[stackPointer] = null;
			breakpoint[stackPointer] = null;
			stackPointer++;
		}

		public void Pop() {
			stackPointer--;
		}

		public void SetExpression(Expression e, Environment env) {
			if (stackPointer==0)
				stackPointer=1;
			expressionStack[stackPointer-1] = e;
			environmentStack[stackPointer-1] = env;
		}

		public void ResetStackForTailCall(Procedure proc, object args) {
			int sp = stackPointer-1;
			expressionStack[sp] = null;
			environmentStack[sp] = null;
			procedureStack[sp] = proc;
			arglistStack[sp] = args;
		}

		public bool BreakpointFrame {
			get {
				Expression breakexp = breakpoint[stackPointer-1];
				return breakexp!=null && breakexp==expressionStack[stackPointer-1];
			}
			set {
				// We need to check here whether the expression is null, in which case
				// the user is staying skip over when they've already entered the procedure
				// so then we really want to set the breakpoint on the previous frame.
				int frame = stackPointer-1;
				if (expressionStack[frame]==null)
					frame--;
				if (frame>=0)
					breakpoint[frame] = value?expressionStack[frame]:null;
			}
		}

		public Procedure CurrentProcedure {
			get {
				if (procedureStack[stackPointer-1] is Procedure)
					return FrameProcedure(stackPointer-1);
				else
					return FrameProcedure(stackPointer-2);
			}
		}

		public Procedure FrameProcedure(int framenumber) {
			object p = procedureStack[framenumber];
			if (p is string)
				return new MethodCallPlaceholder(targetStack[framenumber], (string)p);
			else
				return p as Procedure;
		}

		public Object[] CurrentArguments {
			get {
				return arglistStack[stackPointer-1] as Object[];
			}
		}

		public Expression FindableExpression() {
			for (int i=stackPointer-1; i>=0; i--) {
				Expression e=expressionStack[i];
				while (e!=null) {
					Expression e2 = Expression.FindableExpression(e);
					if (e2!=null)
						return e2;
					e = e.enclosingExpression;
				}
			}
			return null;
		}

		static public void Throw(Exception exception, Expression exp, Environment env) {
			CurrentThreadEvalStack.SetExpression(exp, env);
			throw exception;
		}

		string IInspectable.InspectorName() {
			Exception e = procedureStack[stackPointer-1] as Exception;
			if (e==null)
				return "Execution progress";
			else
				return "Error: "+e.Message;
		}

		IList IInspectable.RenderForInspection() {
			bool isFirstProcedureFrame = true;
			if (renderCache==null) {
				ArrayList continuations = new ArrayList();
				//InspectorField[] continuations = new InspectorField[stackPointer];
				for (int i=0; i<stackPointer; i++) {
					int frameNumber = stackPointer-i-1;
					object e = procedureStack[frameNumber];
					bool isRemote = Utilities.IsRemote(e);

					if (e is string)
						// It's a method call.  Get a placeholder procedure for it.
						e = FrameProcedure(frameNumber);

					if (!isRemote && e is Expression)
						continuations.Add(new InspectorField(null, //continuations.Count.ToString(),
							new PseudoContinuation(e, arglistStack[frameNumber])));
					else if (!isRemote && e is Expression[])
						continuations.Add(new InspectorField(null, //continuations.Count.ToString(),
							new PseudoContinuation(new BeginExpression((Expression[])e), arglistStack[frameNumber])));
					else if (e is Procedure || e is RemoteDataPlaceholder) {
						// This is a call frame;  e is the procedure being called, and environment
						// is its argument list
						object name = e;
						
						if (Utilities.IsRemote(e))
							name = "<RemoteProcedure>";
						else if (e is Procedure) {
							name = ((Procedure)e).name;
							if (name is string)
								name = new StringBox((string)name);
						} else if (e is RemoteDataPlaceholder)
							name = ((RemoteDataPlaceholder)e).PrintedRepresentation;

						object args = arglistStack[frameNumber];

						if (e is RemoteDataPlaceholder)
							e = new ProcedurePlaceholder(name, new object[0]);

						if (name==null)
							name = Symbol.sUnnamed;

						if (args is short) {
							// Args is really an index into argStack.
							short framePointer = (short)args;
							int nargs = (int)argStack[framePointer];

							object[] argList = new object[nargs];
							for (int j=0; j<nargs; j++)
								argList[j] = argStack[framePointer+j+1];
							args = argList;
						}
						// Either way, args is now an array of objects, the argument list.
						if (isFirstProcedureFrame && expressionStack[frameNumber]!=null) {
							CrawlExpressionChain(expressionStack[frameNumber], environmentStack[frameNumber], continuations);
							if (!showAllExpressions)
								isFirstProcedureFrame = false;
						}
						
						object[] realArgs = (object[])args;
						object[] call = SequenceUtils.Append2(new object[1] {name}, (realArgs==null)?new object[0]:realArgs);
						continuations.Add(new InspectorField(null, //continuations.Count.ToString(),
							new CallFrame((Procedure)e, (object[])args, expressionStack[frameNumber], environmentStack[frameNumber]),
							Writer.WriteToString(call), true));
					} else
						continuations.Add(new InspectorField(null, e)); // continuations.Count.ToString(), e));
				}
				renderCache = continuations;
				// Ack.  This is awful.
				for (int pos = 0; pos<renderCache.Count; pos++) {
					((InspectorField)renderCache[pos]).fieldName = (renderCache.Count-pos).ToString();
				}
				return renderCache;
			} else
				return renderCache;
		}

		void CrawlExpressionChain(Expression e, Environment env, ArrayList continuations) {
			if (e != null) {
				continuations.Add(new InspectorField(null, //continuations.Count.ToString(),
					new PseudoContinuation(e, env)));
				if (!(e.enclosingExpression is LambdaExpression))
					CrawlExpressionChain(e.enclosingExpression, env, continuations);
			}
		}
		
		public delegate void DumpHandler(IDictionary dump);
		public static event DumpHandler Dump;

		public void DebugDump(string filename) {
			Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
			try {
				IDictionary dump = new System.Collections.Specialized.ListDictionary();
				if (Dump!=null)
					Dump(dump);
				dump.Add("EvalStack", this);
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				string[] assemblyNames = new string[assemblies.Length];
				for (int i=0; i<assemblyNames.Length; i++)
					assemblyNames[i] = assemblies[i].FullName;
				dump.Add("Assemblies", assemblyNames);
				IFormatter formatter = new BinaryFormatter();
				MissingObjectSurrogate.Install((BinaryFormatter)formatter);
				formatter.Serialize(stream, dump);
			} catch (Exception e) {
				System.Windows.Forms.MessageBox.Show(e.Message, "Error making dump file");
			} finally {
				stream.Close();
			}
		}

		[Serializable]
		public class CallFrame : IInspectable {
			public Procedure procedure;
			public Object[] arguments;
			public Expression currentExpression;
			public Environment currentEnvironment;
			public CallFrame(Procedure procedure, Object[] arguments, Expression currentExpression, Environment currentEnvironment) {
				this.procedure = procedure;
				this.arguments = arguments;
				this.currentExpression = currentExpression;
				this.currentEnvironment = currentEnvironment;
			}
			string IInspectable.InspectorName() {
				if (procedure.name is Symbol)
					return "Call to "+((Symbol)procedure.name).name;
				else
					return "Call to unnamed procedure";
			}

			InspectorField[] CrawlCurrentExpression(int size, Expression e) {
				if (e==null) {
					return new InspectorField[size];
				} else {
					InspectorField[] fields = CrawlCurrentExpression(size+1, e.enclosingExpression);
					fields[size]=new InspectorField(null, new PseudoContinuation(e,currentEnvironment));
					return fields;
				}
			}

			IList IInspectable.RenderForInspection() {
				if (currentExpression==null) {
					return new InspectorField[] {
													new InspectorField("Procedure", procedure),
													new InspectorField("Arguments", arguments)
												};
				} else {
					InspectorField[] fields = CrawlCurrentExpression(3, currentExpression);
					fields[0] = new InspectorField("Procedure", procedure);
					fields[1] = new InspectorField("Arguments", arguments);
					fields[2] = new Separator("Expressions currently pending in this call");
					return fields;
				}
			}
		}

		[Serializable]
		public class PseudoContinuation : IInspectable {
			public PseudoContinuation(object exp, object env) {
				expression = exp;
				environment = env;
				name = Writer.WriteToString(((Expression)expression).Unparse());
			}

			public object expression;
			object environment;
			string name;

			string IInspectable.InspectorName() {
				return "Executing "+name;
			}

			IList IInspectable.RenderForInspection() {
				ArrayList fields = new ArrayList();
				Environment e= (environment!=null)?((Environment)environment):null;
				bool gotOne=false;

				fields.Add(new InspectorField("Expression", expression));
				fields.Add(new InspectorField("Expression type", expression.GetType().Name));
				while (e!=null) {
					object[] names = ((IArglist)e.closure).Arglist;
					object[] data = e.data;
					int i;

					//					fields.Add(new Separator(""));
					if (e.closure.name != null)
						fields.Add(new Separator("Local Variables of "+e.closure.name.ToString()));
					else
						fields.Add(new Separator("Local Variables"));
					gotOne=true;

					for (i=0; i<data.Length; i++)
						fields.Add(new InspectorField(i>=names.Length?"-unknown-":(((Symbol)names[i]).name),
							data[i]));

					e = e.parent;
				}

				if (!gotOne)
					fields.Add(new Separator("No local variables"));
			
				return fields;
			}

			public override string ToString() {
				return name;
			}
		}
	}

	[Serializable]
	public class CallRecord : IInspectable {
		public Procedure Procedure;
		public object[] Arguments;
		object m_result;
		bool complete = false;
		public int depth;

		public object Result {
			get {
				return m_result;
			}
			set {
				m_result = value;
				rendering = null;
				complete = true;
				Meta.TopLevel.gui.RefreshLog();
			}
		}

		string rendering=null;
		public override string ToString() {
			if (rendering==null) {
				rendering = new String(' ', 2*depth) + Writer.SafeWriteToString(new LinkedList(this.Procedure.name, this.Arguments), 10);
				if (complete)
					rendering = rendering + " \u2192 " + ((m_result==Meta.TailCall.theTailCallCookie)?"tail call":Writer.SafeWriteToString(m_result,10));
			}
			return rendering;
		}

		public void Write(Writer w, ref int max) {
			w.stream.Write(new String(' ', 2*depth));
			max -= 1;
			w.Write(new LinkedList(this.Procedure.name, this.Arguments), ref max);
			max -= 1;
			if (complete) {
				w.stream.Write(" \u2192 ");
				if (m_result==Meta.TailCall.theTailCallCookie)
					w.stream.Write("tail call");
				else
					w.Write(m_result);
			}
		}

		public CallRecord(Procedure proc, object[] args) {
			this.Procedure = proc;
			Arguments = args;
			depth = EvalStack.CurrentThreadEvalStack.TraceDepth;
		}
		#region IInspectable Members

		public string InspectorName() {
			return "Procedure call";
		}

		public IList RenderForInspection() {
			if (complete)
				return new InspectorField[3] {
												 new InspectorField("Procedure", this.Procedure),
												 new InspectorField("Arguments", this.Arguments),
												 new InspectorField("Result", Result)
											 };
			else
				return new InspectorField[2] {
												 new InspectorField("Procedure", this.Procedure),
												 new InspectorField("Arguments", this.Arguments)
											 };
		}

		#endregion

	}

	[Serializable]
	public struct ExecutionMode {
		[FlagsAttribute]
			[Serializable]
			public enum Flags : byte {
			Normal = 0,
			Breakpoint = 1,
			Trace = 2
		}

		ExecutionMode(Flags m) {
			mode = m;
		}

		public Flags mode;
		public bool Breakpoint {
			get {
				return (mode&Flags.Breakpoint)!=0;
			}
			set {
				if (value)
					mode |= Flags.Breakpoint;
				else
					mode &= ~Flags.Breakpoint;
			}
		}
		public bool Trace {
			get {
				return (mode&Flags.Trace)!=0;
			}
			set {
				if (value)
					mode |= Flags.Trace;
				else
					mode &= ~Flags.Trace;
			}
		}

		public bool CheckNormal() {
			return ((mode|TopLevel.GlobalExecutionMode.mode)&Flags.Breakpoint)==0;
		}
		public bool CheckNormal(ExecutionMode proc) {
			return ((mode|TopLevel.GlobalExecutionMode.mode|proc.mode)&Flags.Breakpoint)==0;
		}
		public ExecutionMode GetGlobalMode() {
			return new ExecutionMode(mode|TopLevel.GlobalExecutionMode.mode);
		}
		public bool IsNormal {
			get {
				return mode==Flags.Normal;
			}
		}
	}

	public class MethodCallPlaceholder : ApplyInterfaceProcedure {
		public object target;
		public string methodName;

		public MethodCallPlaceholder(object target, string name) {
			this.target = target;
			methodName = name;
			if (target is Type)
				this.name = ((Type)target).Name +"." + methodName;
			else
				this.name = Writer.SafeWriteToString(target,20)+"."+methodName;
		}

		public override object Apply(object[] args) {
			throw new BadProcedureException(this);
		}

	}

	// Forces a string to print as if it were a symbol
	public class StringBox {
		string s;
		public StringBox(string s) {
			this.s = s;
		}
		public override string ToString() {
			return s;
		}
	}
}